import { Vector2, EventDispatcher } from 'three'
import { getCameraRaycast } from './view'
import MapEvent from '../model/map-event'

const __preHover__ = new WeakMap()
// const __pickMode__ = new WeakMap()
const __pickSelect__ = new WeakMap()

function eventMixin(Class) {
    if (!Class.prototype.dispatchEvent) {
        let {addEventListener, hasEventListener, removeEventListener, dispatchEvent} = EventDispatcher.prototype;
        Object.assign(Class.prototype, {addEventListener, hasEventListener, removeEventListener, dispatchEvent})
    }
    Object.assign(Class.prototype, {
        hasEventListener: function(type, listener) {
            if (this._listeners === undefined) return false
            var listeners = this._listeners
            return listeners[type] !== undefined && (!listener || listeners[type].indexOf(listener) !== -1)
        },
    })
    const eventMap = new Map()
    const bindEvent = (mo, eventType, fn, once) => {
        let thisMap = eventMap.get(mo)
        if (!thisMap) {
            thisMap = new Map()
            eventMap.set(mo, thisMap)
        }
        let listener = event => {
            once && mo.off(eventType, fn)
            fn(event.message)
        }
        thisMap.set(fn, listener)
        mo.addEventListener(eventType, listener)
    }
    Object.assign(Class.prototype, {
        on(eventType, fn) {
            bindEvent(this, eventType, fn)
        },
        once(eventType, fn) {
            bindEvent(this, eventType, fn, true)
        },
        off(eventType, fn) {
            let thisMap = eventMap.get(this)
            if (thisMap) {
                let listener = thisMap.get(fn)
                if (listener) {
                    thisMap.delete(fn)
                    this.removeEventListener(eventType, listener)
                }
            }
        },
        // pickStart() {
        //     __pickMode__.set(this, true)
        //     this.$mapWrapper.classList.add('picking')
        // },
        // pickEnd() {
        //     this.$mapWrapper.classList.remove('picking')
        //     __pickMode__.delete(this)
        // },
    })
}

const mouse = new Vector2()
function intersectObjects(eventType, mo, e) {
    if (!mo.mapScene) {
        return;
    }
    mouse.set(e.x, e.y);

    if (typeof eventType === 'string') eventType = [eventType]
    let objects = Array.from(mo.mapScene.floors)
    // if (!__pickMode__.get(mo)) {
    //     objects.splice(
    //         0,
    //         0,
    //         ...Array.from(mo._overlays)
    //             .filter(
    //                 it =>
    //                     it.object3D &&
    //                     it.hasEventListener &&
    //                     eventType.find(it2 => it.hasEventListener(it2)) &&
    //                     it.object3D.parent &&
    //                     it.object3D.parent.visible
    //             )
    //             .map(it => it.object3D)
    //     )
    // }
    objects = objects.filter(it => it.visible)

    let raycaster = getCameraRaycast(mo, mouse)
    let intersects = raycaster.intersectObjects(objects, false)
    let top = intersects.find(it => it.object.isFloor)
    if (top) {
        let intersectsRooms = raycaster.intersectObjects(top.object.rooms, false)
        intersectsRooms && intersects.push(...intersectsRooms)
    }
    return intersects
}

// function pickOverlay(intersects) {
//     let overlays = intersects.filter(it => it.object.handler && it.object.handler.isOverlay)
//     let fronts = overlays.filter(it => !it.object.material.depthTest)
//     let floor = intersects.find(it => it.object.isFloor)
//     let overlay
//     if (fronts.length > 0) {
//         overlay = fronts[0]
//     } else if (overlays.length > 0) {
//         overlay = !floor || floor.distance + 1 > overlays[0].distance ? overlays[0] : undefined
//     }
//     if (overlay) {
//         overlay = overlay.object.handler
//     }
//     return overlay
// }

function dispatchMapEvent(mo, type, e, intersects, overlay) {
    if (mo.hasEventListener(type)) {
        let floor = intersects && intersects.find(it => it.object.isFloor)
        let room = intersects && intersects.find(it => it.object.isRoom)
        if (floor) {
            mo.dispatchEvent({
                type,
                message: new MapEvent({
                    type,
                    x: Math.round(floor.point.x) + 0,
                    y: Math.round(floor.point.y) + 0,
                    floor: floor,
                    room: room,
                    target: mo,
                    currentTarget: overlay,
                    domEvent: e,
                    address: `${mo.mapScene.name} ${floor.object.name} ${(room && room.object.name) || ''}`.trim(),
                    outside: false
                }),
            })
        } else {
            mo.dispatchEvent({
                type,
                message: new MapEvent({
                    type,
                    domEvent: e,
                    currentTarget: overlay,
                    outside: true
                }),
            })
        }
    }
}

// function updateCursor(mo, intersects) {
//   let clickable = false;
//   if (intersects && intersects.length > 0) {
//     intersects = intersects.filter(
//       it => !it.object.handler || !it.object.handler.isOverlay || it.object.handler.hasEventListener('click')
//     )
//     if (intersects.length > 0) {
//       clickable = true;
//     }
//   }
//   mo.$mapWrapper.classList[clickable ? 'add' : 'remove']('clickable')
// }

function initEvents(mo) {
    mo.gestureControl.onClickListener = e => {
        // if (__pickMode__.get(mo)) return;
        let preSelect = __pickSelect__.get(mo);
        function clearPreSelect() {
          if (preSelect) {
            if (preSelect.object && preSelect.object.onSelect) preSelect.object.onSelect(false);
            __pickSelect__.delete(mo);
          }
        }
        let eventType = e.clickType === 0 ? 'click' : 'rightClick'
        let intersects = intersectObjects(eventType, mo, e)
        // if (!intersects || intersects.length === 0) {
        //     //dispatchMapEvent(mo, eventType, e.rawEvent)
        //     return
        // }
        // let overlay = pickOverlay(intersects)
        // if (overlay) {
        //     overlay.dispatchEvent({ type: eventType, message: new MapEvent({ target: overlay, domEvent: e.rawEvent }) })
        // }
        let top = null;
        if (intersects && intersects.length > 0) {
          top = intersects[intersects.length - 1];
        }
        if (preSelect != top) {
          clearPreSelect();
          if (top && (e.clickType === 0)) {
            __pickSelect__.set(mo, top)
            if (top.object && top.object.onSelect) top.object.onSelect(true);
          }
        }

        dispatchMapEvent(mo, eventType, e.rawEvent, intersects, top)
    }
    mo.gestureControl.onHoverListener = e => {
        let preHover = __preHover__.get(mo)
        function clearPreHover() {
          mo.$mapWrapper.classList.remove('clickable');
          if (preHover) {
            if (preHover.object && preHover.object.onHover) preHover.object.onHover(false);
            __preHover__.delete(mo);
          }
        }
        let intersects = intersectObjects(['hover', 'click'], mo, e)
        //updateCursor(mo, intersects)
        //let overlay = null
        //if (intersects && intersects.length > 0) {
        //    overlay = pickOverlay(intersects)
        //}
        let top = null;
        if (intersects && intersects.length > 0) {
          top = intersects[intersects.length - 1];
        }
        if (preHover != top) {
          clearPreHover()
          if (top) {
            __preHover__.set(mo, top)
            if (top.object && top.object.onHover) {
              mo.$mapWrapper.classList.add('clickable');
              top.object.onHover(true);
            }
          }
        }

        dispatchMapEvent(mo, 'hover', e.rawEvent, intersects, top)
    }
}

export { eventMixin, initEvents }
